The need for computer security steadily increases as computer systems become more complex, networks become more distributed, and Internet users proliferate around the world. As vulnerabilities in computer systems are detected, those who detect the vulnerability (e.g., hackers) often create malicious scripts that exploit the newly found vulnerability and make these scripts publicly available on the Internet, e.g., on an anonymous FTP site. Less savvy computer users, sometimes referred to as “script kiddies,” can then freely download the malicious script and attack other computer systems using the script's prepackaged capabilities.
A known vulnerability in most computer systems is a vulnerability to buffer overflow attacks. Buffer overflow attacks, generally, exploit a lack of error checking when programs write data to temporary buffer arrays allocated in the stack. Malicious programs attempt to write data to buffer arrays in order to either place the malicious code in the victim program's address space or to manipulate pointers to point to malicious code already there. During a buffer overflow attack, an attacker arranges for suitably malicious code to be present in a victim program's address space, and gets the program to jump to the malicious code with suitable parameters loaded into the program's registers and memory. When the malicious code is executed, software executing on the computer or filed stored on the computer can become corrupted, deleted, or used as the basis for launching another attack, and ultimately the system is compromised.
An attacker can either inject malicious code through string input, or exploit already present code in an unintended manner. Because many programs do not verify that data written to a buffer is smaller than the buffer's actual size, a hacker can manipulate a program to write data beyond the end of the buffer's allocated memory (i.e., overflow the buffer), which may then execute on or alter the system in which it is written. Programs written in C and C++ are particularly vulnerable to buffer overflow attacks because the C and C++ programming languages typically perform no array bounds checking. As a result, a hacker can inject malicious executable binary code specific to the machine being attacked by writing data past the end of a buffer array. The hacker can also make arbitrary changes to program state data stored adjacent to the array, such as changing a pointer to a return address, in order to alter the program's control flow to point to the malicious code.
With reference to FIG. 1, the most common data structure attacked using the buffer overflow technique is the runtime stack. These attacks are known in the art and are often referred to as stack buffer overflow or stack smashing attacks. When a program calls a function, the program typically pushes the function's arguments onto the stack (from right to left if the program is written in “C or C++”), and then pushes the return address onto the stack. For example, in FIG. 1, stack (A) results from the function call foo (argument3, argument2, argument1). The function might also declare automatic local arrays and variables, as necessary, after the function is executed but before the variables are used, as shown in FIG. 1, stack (B).
Suppose the function foo accepts a string packet from a remote machine and copies the string onto the stack frame. If foo only accepts fixed length strings, buffer overflow might not be a problem. However, if foo accepts a variable length string with length n and copies it into a fixed length array whose size is less than n, then the stack frame becomes corrupted. An attacker can overflow an automatic variable (e.g., local variable 1) local to the function to overwrite the return address, such as is shown in FIG. 1, stack (B), to point to attack code. When the victim function returns and dereferences the return address, the attacked program will jump to the attack code, and execute whatever malicious code the attacker injected into the stack.
As stated above, the attacker may inject the attack code, or exploit already present code in an unintended manner. If the attacker injects the attack code, he or she may do so by overflowing an input variable, or by simply including it within an input variable when the expected size of the input variable is large enough to accommodate the malicious code. Alternatively, the attacker can alter a return address to point to already present code. For example, if the attack code wants to execute a root shell, the attacker may alter the return address to point to preexisting code that executes a root shell, such as exec (“/bin/sh”), as is known in the art.
Known stack buffer overflow attacks include attacks on activation records, function pointers, and longjmp buffers. The common requirement for each attack is the need to alter a program's control flow. As stated above, an attacker typically alters the control flow by overflowing a buffer to overwrite a return address of a function call. Various known solutions have attempted to prevent malicious code from overwriting return addresses, with limited success.
One known solution is to make the runtime stack non-executable. That is, data stored in the stack portion of a user program's address space is prevented from executing. As a result, injected attack code also cannot be executed. While the approach offers a minimal performance penalty, the necessary patches are non-trivial to implement, and require considerable financial and personnel development resources. In addition, this solution unnecessarily prevents all data in the stack from executing, even data that is known to be safe.
Another known solution is to perform integrity checks on the stack when a program is compiled. For example, StackGuard (commercially available from WireX Communications, Inc. of Portland, Oreg.) provides a compiler extension that checks stack integrity when a program is compiled, and plugs any potential buffer overflow vulnerabilities. StackGuard inserts instructions to detect when a return address has been altered before returning from a function (and subsequently executing an attacker's code). However, in order to benefit from a compiler enhancement such as StackGuard, a program must be recompiled using the enhanced compiler. Thus, every vulnerable program on a system would need to be recompiled in order to completely protect that system against buffer overflow attacks. Due to the vast amount of legacy programs already present on computer systems, and the large number of programs still being developed without proper array bounds checking, this solution would require vast amounts of time and resources to effectively implement.
Still other known solutions use a canary mechanism on each function call. That is, the system injects a random number between the function call's last argument and the return address. The system assumes that the return address is unaltered if and only if the canary (random) number is unaltered. However, canary mechanisms often do not work properly when used with a debugger, requiring developers to expend additional development resources (personnel time, processor time, etc.).
Thus, it would be an advancement in the art to provide a solution that protects computer systems from buffer overflow attacks without requiring that each vulnerable program on the computer system be recompiled. It would be a further advancement in the art to provide a solution that does not disrupt debuggers used in software development cycles.